import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../env/environment_service.dart';
import '../../model/play/video_detail_model.dart';
import '../../services/storage_service.dart';
import '../logger.dart';
import 'm3u8_download_notify.dart';
import 'm3u8_download_notify_state.dart';
import 'm3u8_download_record.dart';
import 'm3u8_download_record_state.dart';
import 'm3u8_downloader.dart';

const _tryCountMax = 5; // 重试次数
const _concurrencyCount = 3; // 下载并发数

class _M3u8DownloadManager {
  final _downloading = <int>{};
  final _downloadingDownloader = <int, M3u8Downloader>{};
  // <recordId, notifuer>
  final _notifiers = <int, ValueNotifier<M3u8DownloadNotify>>{};

  StorageService get _storageService => Get.find<StorageService>();

  void _d(String m) {
    if (Environment.enableM3u8DownloadLog) {
      logger.d('[M3u8DownloadManager] $m');
    }
  }

  void loadRecords() {
    final records = _storageService.allM3u8DownloadRecords;
    _d('loadRecords start total len:${records.length}');
    for (final e in records) {
      if (e.isFininshed) {
        _d('loadRecords videoId:${e.videoId},state:${e.state},desc:${e.stateDesc} skip.');
        continue;
      }
      register(e);
      _d('loadRecords register videoId:${e.videoId},state:${e.state}');
    }
  }

  // 时间倒序
  Iterable<M3u8DownloadRecord> get allRecordsByCreatedAtDesc =>
      _storageService.allM3u8DownloadRecords
          .sorted((a, b) => b.createdAt.difference(a.createdAt).inMicroseconds);
  // 时间正序
  Iterable<M3u8DownloadRecord> get _allRecordsByCreatedAtAsc =>
      _storageService.allM3u8DownloadRecords
          .sorted((a, b) => a.createdAt.difference(b.createdAt).inMicroseconds);

  M3u8DownloadRecord? getRecord(int videoId) =>
      _storageService.getM3u8DownloadRecord(videoId);

  Future<void> _deleteRecord(int videoId) =>
      _storageService.deleteM3u8DownloadRecord(videoId);

  bool hasRecord(int videoId) => getRecord(videoId) != null;

  /// 只能获取到waiting和downloading的阶段的notifier
  ValueNotifier<M3u8DownloadNotify>? getNotifier(int videoId) =>
      _notifiers[videoId];

  ValueNotifier<M3u8DownloadNotify> getNotifierAlways(int videoId) {
    var notifier = getNotifier(videoId);
    if (notifier != null) return notifier;
    // 返回一次性notifier
    M3u8DownloadNotifyState notifyState = M3u8DownloadNotifyState.none;
    final record = getRecord(videoId);
    if (record != null) {
      if (record.isSuccess) {
        notifyState = M3u8DownloadNotifyState.success;
      } else if (record.isError) {
        notifyState = M3u8DownloadNotifyState.error;
      } else if (record.isCancel) {
        notifyState = M3u8DownloadNotifyState.cancel;
      } else {
        // 应该是不会到这里，在上面 getNotifer就会返回
        assert(false,
            'error occur videoId:${record.videoId} state is ${record.state}, but not notifer');
      }
    }
    notifier = ValueNotifier<M3u8DownloadNotify>(
      M3u8DownloadNotify(notifyState),
    );
    Future.delayed(Duration.zero, () => notifier!.dispose());
    return notifier;
  }

  void _disposeNotifier(int videoId) {
    final notifier = _notifiers[videoId];
    if (notifier == null) return;

    notifier.dispose();
    _notifiers.remove(videoId);
  }

  ValueNotifier<M3u8DownloadNotify> _initNotifier(int videoId) {
    final notifier = _notifiers[videoId];
    if (notifier == null) {
      _notifiers[videoId] = ValueNotifier<M3u8DownloadNotify>(
        M3u8DownloadNotify(M3u8DownloadNotifyState.waiting),
      );
    }
    return _notifiers[videoId]!;
  }

  ValueNotifier<M3u8DownloadNotify>? register(M3u8DownloadRecord record) {
    final old = getRecord(record.videoId);
    if (old != null) {
      if (old.isFininshed) {
        _deleteRecord(record.videoId);
      }
    } else {
      _storageService.saveM3u8DownloadRecord(record);
    }

    final notifier = _initNotifier(record.videoId);
    if (record.isWaiting) {
      _tryStartNext();
    }
    return notifier;
  }

  M3u8DownloadRecord? _tryPushToDownloading() {
    if (_downloading.length >= _concurrencyCount) return null;
    for (final e in _allRecordsByCreatedAtAsc) {
      if (e.isWaiting && !_downloading.contains(e.videoId)) {
        _downloading.add(e.videoId);
        return e;
      }
    }
    return null;
  }

  void _downloadingFinish(int videoId, M3u8DownloadNotifyState notifyState) {
    getNotifier(videoId)?.value = M3u8DownloadNotify(notifyState);
    _disposeNotifier(videoId);
    _downloading.remove(videoId);
    _downloadingDownloader[videoId]?.cancel();
    _downloadingDownloader.remove(videoId);
    _tryStartNext();
  }

  void _tryStartNext() {
    final record = _tryPushToDownloading();
    if (record == null) return;
    final fullUrl = record.fullUrl;
    if (fullUrl == null) {
      record.state = M3u8DownloadRecordStateEnum.error;
      record.stateDesc = '下载链接错误';
      _storageService.saveM3u8DownloadRecord(record);
      _downloadingFinish(record.videoId, M3u8DownloadNotifyState.error);
      _d('bad url videoId:${record.videoId}');
      return;
    }

    final downloader = M3u8Downloader(
      fullUrl,
      videoId: record.videoId,
      onProgress: (downloadedTime, totalTime) {
        final rec = getRecord(record.videoId);
        if (rec == null) return;
        if (!rec.isWaiting) {
          assert(false, 'onProgress invoked when isProcessing');
          return;
        }
        getNotifier(rec.videoId)?.value = M3u8DownloadNotify(
          M3u8DownloadNotifyState.downloading,
          downloadedTime: downloadedTime,
          totalTime: totalTime,
        );
      },
      onDone: (filepath) {
        final rec = getRecord(record.videoId);
        if (rec == null) return;
        rec.state = M3u8DownloadRecordStateEnum.success;
        rec.downloadFilepath = filepath;
        _storageService.saveM3u8DownloadRecord(rec);
        _d('download success videoId:${rec.videoId}, filepath:${rec.downloadFilepath}');
        _downloadingFinish(rec.videoId, M3u8DownloadNotifyState.success);
      },
      onCancel: () {
        final rec = getRecord(record.videoId);
        if (rec == null) return;
        rec.state = M3u8DownloadRecordStateEnum.cancel;
        _storageService.saveM3u8DownloadRecord(rec);
        _downloadingFinish(rec.videoId, M3u8DownloadNotifyState.cancel);
      },
      onError: (desc) {
        final rec = getRecord(record.videoId);
        if (rec == null) return;
        rec.state = M3u8DownloadRecordStateEnum.error;
        rec.stateDesc = desc;
        _storageService.saveM3u8DownloadRecord(rec);
        _downloadingFinish(rec.videoId, M3u8DownloadNotifyState.error);
      },
      tryMax: _tryCountMax,
    );
    _downloadingDownloader[record.videoId] = downloader;
    downloader.start();
  }

  ValueNotifier<M3u8DownloadNotify>? registerByDetail(VideoDetail detail) =>
      register(M3u8DownloadRecord.videoDetail(detail));

  void _doCancel(int videoId) {
    final rec = getRecord(videoId);
    if (rec == null) return;
    rec.state = M3u8DownloadRecordStateEnum.cancel;
    _storageService.saveM3u8DownloadRecord(rec);
    _downloadingFinish(rec.videoId, M3u8DownloadNotifyState.cancel);
  }

  // 取消
  void cancel(int videoId) => _doCancel(videoId);
  // 取消->等待中
  bool resetToWaiting(int videoId) {
    final rec = getRecord(videoId);
    if (rec == null) return false;
    if (rec.state == M3u8DownloadRecordStateEnum.cancel ||
        rec.state == M3u8DownloadRecordStateEnum.error) {
      rec.state = M3u8DownloadRecordStateEnum.waiting;
      _storageService.saveM3u8DownloadRecord(rec);
      _tryStartNext();
      return true;
    }
    return false;
  }
}

// ignore: non_constant_identifier_names
final M3u8DownloadManager = _M3u8DownloadManager();
